/******************************************************************
 * @version      : 1.0
 * @Author       : zhl
 * @Date         : 2023-05-16 14:33:09
 * @LastEditors  : zhl
 * @E-Mail       : zhll168@163.com
 * @Description  : 业务描述
 ******************************************************************/
#include <unordered_map>
#include <typeinfo>
#include <iostream>
#include <unistd.h>
#include <cstddef>
#include <sys/timeb.h>
#include <regex>
#include <thread>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>

#include "upgrade.h"
#include "common.h"
#include "CLogger.h"
#include "sysconfig.h"
#include "Settings.h"
#include "FileUpload.h"
#include "JsonManager.h"
#include "SysinfoManager.h"
#include "FileDownload.h"
#include "upgrade_data.h"
#include "CThreadNotify.h"
#include "CommandManager.h"
#include "CMosquittoManager.h"


#define CLOUD_TO_DEV_TOPIC(sn)   "c2d/"+sn+"/command/default/v1"
#define DEV_TO_CLOUD_TOPIC(sn)   "d2c/"+sn+"/command/default/v1"

#define PEMD_SETTINGS_FILE_PATH  "/usr/local/pemd/conf/settings.conf"
#define UPGRADE_SETTINGS_FILE_PATH "/usr/local/upgrade/conf/settings.conf"


#define ROOT_DIR               "."
#define CURRENT_TIME_STRING_LEN  32
#define FILENAME_LEN  256


char     mosquitto_addr[64] = "172.17.0.1";
uint16_t mosquitto_port = 1883;
char     cloud_mosquitto_addr[64] = "39.99.149.250";
uint16_t cloud_mosquitto_port = 1883;
char     cloud_username[32] = "admin";
char     cloud_password[32] = "8048";
// char     download_url[1024] = "http://10.1.12.30/LanSee.exe";
char     filename[FILENAME_LEN] = "/tmp/upgrade/upgrade.tar.gz";

int      log_level = SPDLOG_LEVEL_INFO;
int      log_size  = 16*1024*1024;
char     log_filename[256] = ROOT_DIR"/log/upgrade.log";

int      upgrade_target = 0;
int      keepalive_time = 600;

char     g_sn[32] = "0000000000000000";
char     pemd_ver[32] = "0.0.0";
int      pemd_code = 0;
char     datacenter_ver[32] = "0.0.0";
int      datacenter_code = 0;
char     uartproxy_ver[32] = "0.0.0";
int      uartproxy_code = 0;
char     firmware_ver[32] = "0.0.0";
int      firmware_code = 0;
bool     g_is_connected = false;

CThreadNotify g_upgrade_thread_notify;
CThreadNotify g_keepalive_thread_notify;
UPGRADE_REQ_MSG_TYPE g_upgrade_req_msg; 
std::shared_ptr<CMosquittoManager> g_mosquitto_manager = std::make_shared<CMosquittoManager>();



int get_dev_sn(char *sn, int sn_len){
    if((NULL == sn) || (1 > sn_len)){
        return -1;
    }
    get_sn(sn, sn_len);
    if(0 < sn_len){
        return sn_len;
    }else{
        return getDevSN(sn, sn_len);
    }
}

uint64_t get_timestamp(){
    struct timeval tp;
    gettimeofday(&tp, NULL);
    uint64_t ms = (uint64_t)tp.tv_sec * 1000 + (uint64_t)tp.tv_usec / 1000;
    return ms;
}

void usage(){
    printf("============================================\n");
    printf("-k    set upgrade target.\n");
    printf("-r    set url file.\n");
    printf("-d    set mqtt broker cloud ip address.\n");
    printf("-q    set mqtt broker cloud port.\n");
    printf("-u    set mqtt broker user name.\n");
    printf("-s    set mqtt broker password.\n");
    printf("-l    set log level.\n");
    printf("-n    set log size.\n");
    printf("-c    set config file path.\n");
    printf("-t    set upgrade file name.\n");
    printf("-e    set report interval time.\n");
    printf("-v    display upgrade app's version.\n");
    printf("-h    help.\n");
    printf("============================================\n");
}
//命令行解析
int parse_cmd(int argc, char* argv[]){
    int ch   = -1;
    while(-1 != (ch = getopt(argc, argv, "a:p:l:r:t:k:e:vc:?h"))){
        switch(ch){
            case 'a': //mosquitto ip addr
                bzero(mosquitto_addr, sizeof(mosquitto_addr));
                strncpy(mosquitto_addr, optarg, sizeof(mosquitto_addr)-1);
            break;
            case 'p':
                mosquitto_port = atoi(optarg);
            break;
            case 'l':
                log_level = atoi(optarg);
                if((6 < log_level) || (0 > log_level)){
                    log_level = SPDLOG_LEVEL_INFO;
                }
            break;
            case 'n':
                log_size = atoi(optarg);
                if(0 > log_size){
                    log_size = 16*1024*1024;
                }
            break;
            case 'f': //log file path
                bzero(log_filename, sizeof(log_filename));
                strncpy(log_filename, optarg, sizeof(log_filename)-1);
            break;
            case 'd': //cloud ip addr
                bzero(cloud_mosquitto_addr, sizeof(cloud_mosquitto_addr));
                strncpy(cloud_mosquitto_addr, optarg, sizeof(cloud_mosquitto_addr)-1);
            break;
            case 'q':
                cloud_mosquitto_port = atoi(optarg);
            break;
            case 'u':
                bzero(cloud_username, sizeof(cloud_username));
                strncpy(cloud_username, optarg, sizeof(cloud_username)-1);
            break;
            case 's':
                bzero(cloud_password, sizeof(cloud_password));
                strncpy(cloud_password, optarg, sizeof(cloud_password)-1);
            break;
            case 'k': //
                upgrade_target = atoi(optarg);
                if(0 > upgrade_target){
                    upgrade_target = 100;
                }
            break;
            case 'e': //
                keepalive_time = atoi(optarg);
                if(1 > keepalive_time){
                    upgrade_target = 600;
                }
            case 'r': {//
                char download_url[1024] = {0};
                bzero(download_url, sizeof(download_url));
                strncpy(download_url, optarg, sizeof(download_url)-1);
                g_upgrade_req_msg.data.uri = download_url;
            }
            break;
            case 't': //
                bzero(filename, sizeof(filename));
                strncpy(filename, optarg, sizeof(filename)-1);
            break;
            case 'v':
                printf("upgrade version %s,build time:%s,%s\n", (char *)"1.0",__DATE__,__TIME__);
                return -1;
            case '?': //
            case 'h': //help
            default:
                usage();
                return -1;
            break;
        }
    }
    return 0;
}

void init_settings(){
    Settings::getInstance().open(UPGRADE_SETTINGS_FILE_PATH);
    std::string value;
    if(0 == Settings::getInstance().read("mosquitto_addr", value)){
        snprintf(mosquitto_addr, sizeof(mosquitto_addr), "%s", value.c_str());
    }
    //
    if(0 == Settings::getInstance().read("mosquitto_port", value)){
        int port = atoi(value.c_str());
        if(0 < port){
            mosquitto_port = port;
        }
    }
    if(0 == Settings::getInstance().read("cloud_mosquitto_addr", value)){
        snprintf(cloud_mosquitto_addr, sizeof(cloud_mosquitto_addr), "%s", value.c_str());
    }
    //
    if(0 == Settings::getInstance().read("cloud_mosquitto_port", value)){
        int port = atoi(value.c_str());
        if(0 < port){
            cloud_mosquitto_port = port;
        }
    }
    //
    if(0 == Settings::getInstance().read("cloud_username", value)){
        snprintf(cloud_username, sizeof(cloud_username), "%s", value.c_str());
    }
    //
    if(0 == Settings::getInstance().read("cloud_password", value)){
        snprintf(cloud_password, sizeof(cloud_password), "%s", value.c_str());
    }
    //
    Settings::getInstance().close();
}


char* get_current_time_str(char *curTimeStr){
    struct timeb tv;
    struct tm *t;
    ftime(&tv);
    t = localtime(&tv.time);

    snprintf(curTimeStr, CURRENT_TIME_STRING_LEN, "%d%02d%02d%02d%02d%02d%03u", 1900+t->tm_year, 1+t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, tv.millitm);
    return curTimeStr;
}

char* get_dir(char *dir, char *curTimeStr){
    if(NULL == dir){
        return dir;
    }
    if((NULL == curTimeStr) || (1 > strlen(curTimeStr))){
        return dir;
    } else {
        snprintf(dir, FILENAME_LEN, "/tmp/upgrade/%s", curTimeStr);
    }
    if(0 != access(dir, F_OK)){
        std::string cmd = "mkdir -p " + std::string(dir);
        system(cmd.c_str());
    }
    return dir;
}
char* get_file_name(char *curTimeStr, char *fname){
    if(NULL == fname){
        return NULL;
    }
    std::string fn = "/tmp/upgrade";
    if((NULL == curTimeStr) || (1 > strlen(curTimeStr))){
        fn += "upgrade.tar.gz";
    } else {
        fn += std::string(curTimeStr)+".tar.gz";
    }

    snprintf(fname, FILENAME_LEN, "%s", fn.c_str());
    return fname;
}

void upgrade_signal(){
    g_upgrade_thread_notify.lock();
    g_upgrade_thread_notify.signal();
    g_upgrade_thread_notify.unlock();
}

CURLcode download(UPGRADE_REQ_MSG_TYPE req, std::string fname){
    if(fname.empty() || req.data.uri.empty()){
        return CURLE_BAD_DOWNLOAD_RESUME;
    }
    
    FileDownload::getInstance().init(fname.c_str(), req.data.username.c_str(), req.data.password.c_str());
    CURLcode err = FileDownload::getInstance().download(req.data.uri);

finish:
    FileDownload::getInstance().finish();
    return err;
}

int upgrade(std::string fname, std::string dir){
    if(fname.empty()){
        return -1;
    }
    std::string cmd = "if [ -d " + dir + " ]; then rm -rf " + dir +"; fi "
                      + "&& mkdir -p " + dir +" && if [ -f " + fname + " ]; then tar -zxvf " + fname + " -C " + dir
                      +"; rm -rf " + fname + "; fi && cd " + dir + " && ./update.sh; rm -rf " + dir;
    CLogger::getInstance()->LOG_I("upgrade cmd: %s", cmd.c_str());
    return system(cmd.c_str());
}
void read_pemd_version(){
    if(0 > Settings::getInstance().open(PEMD_SETTINGS_FILE_PATH)){
        return ;
    }
    std::string value;
    if(0 == Settings::getInstance().read("software_ver", value)){
        snprintf(pemd_ver, sizeof(pemd_ver), "%s", value.c_str());
    }
    //
    if(0 == Settings::getInstance().read("software_code", value)){
        pemd_code = atoi(value.c_str());
    }
    Settings::getInstance().close();
}

void read_datacenter_version(){
    datacenter_code = 0;
    bzero(datacenter_ver, sizeof(datacenter_ver));
    snprintf(datacenter_ver, sizeof(datacenter_ver), "0.0.0");
}

void read_uartproxy_version(){
    uartproxy_code = 0;
    bzero(uartproxy_ver, sizeof(uartproxy_ver));
    snprintf(uartproxy_ver, sizeof(uartproxy_ver), "0.0.0");
}

void read_firmware_version(){
    firmware_code = 0;
    bzero(firmware_ver, sizeof(firmware_ver));
    snprintf(firmware_ver, sizeof(firmware_ver), "0.0.0");
}

void read_version(){
    read_pemd_version();
    read_datacenter_version();
    read_uartproxy_version();
    read_firmware_version();
}
int subscribe_topics(){
    std::string c2d_topic = CLOUD_TO_DEV_TOPIC(std::string(g_sn));
    int ret =  g_mosquitto_manager->subscribe(c2d_topic.c_str());
    if(MOSQ_ERR_SUCCESS != ret){
        return MOSQ_ERR_NOT_SUPPORTED;
    }
    return MOSQ_ERR_SUCCESS;
}

int send_response(std::string command, std::string topic, uint64_t req_id, int code, int64_t upgrade_id, std::string msg){
    if((nullptr == g_mosquitto_manager) || (topic.empty())){
        return -1;
    }
    UPGRADE_REP_MSG_TYPE rep;
    rep.command = command;
    rep.command_id = req_id;
    rep.command_type = "REP";
    rep.sn = g_sn;
    rep.timestamp = get_timestamp();
    rep.data.code = code;
    rep.data.msg = msg;
    std::string rep_msg = JsonManager::getInstance().get_response_msg(rep);
    return g_mosquitto_manager->publish(topic.c_str(), rep_msg.length(), rep_msg.c_str());
}
int handle_message(std::string msg){
    MSG_HEADER msg_head= JsonManager::getInstance().get_msg_head(msg);
    if(!msg_head.command.empty() && (0 == msg_head.command_type.compare("REQ"))){
        if(0 == msg_head.command.compare("CMD_UPGRADE")){
            g_upgrade_req_msg = JsonManager::getInstance().get_upgrade_info(msg);
            upgrade_signal();
        } else if(0 == msg_head.command.compare("CMD_GET_LOG")){
            GET_LOG_REQ req = JsonManager::getInstance().get_log_req(msg);
            send_response(req.command, DEV_TO_CLOUD_TOPIC(std::string(g_sn)), req.command_id, 0, -1, "收到日志召回指令");
            if(-1 < req.data.type){
                    FileUpload::getInstance().upload_log_file(req.data.type, req.command_id);
            }
        } else if(0 == msg_head.command.compare("CMD_REPORT_LOG")){
            //
            REPORT_LOG_REQ_OR_REP report_log_msg = JsonManager::getInstance().report_log_req(msg);
            send_response(report_log_msg.command, DEV_TO_CLOUD_TOPIC(std::string(g_sn)), report_log_msg.command_id, 0, -1, "收到请求下一包指令");
            if((0 < report_log_msg.data.packet_num) || (report_log_msg.data.packet_num <= report_log_msg.data.packet_total_num)){
                FileUpload::getInstance().get_packet(report_log_msg.data.packet_num, report_log_msg.command_id);
            }
        } else if(0 == msg_head.command.compare("CMD_EXECUTE_COMMAND")){
            EXECUTE_CMD_TYPE cmd = JsonManager::getInstance().cmd_execute_req(msg);
            send_response(cmd.command, DEV_TO_CLOUD_TOPIC(std::string(g_sn)), cmd.command_id, 0, -1, "收到命令执行请求");
            CommandManager cm;
            cm.init(g_mosquitto_manager, g_sn);
            cm.exec_cmd(cmd.data.cmd, cmd.command_id);
        } else {
            CLogger::getInstance()->log_i("未知消息");
            return -1;
        }
    } else if((0 == msg_head.command.compare("CMD_REPORT_LOG")) && (0 == msg_head.command_type.compare("REP"))){
            //
            REPORT_LOG_REQ_OR_REP report_log_msg = JsonManager::getInstance().report_log_req(msg);
            send_response(report_log_msg.command, DEV_TO_CLOUD_TOPIC(std::string(g_sn)), report_log_msg.command_id, 0, -1, "收到应答，请求下一包");
            if((0 < report_log_msg.data.packet_num) || (report_log_msg.data.packet_num <= report_log_msg.data.packet_total_num)){
                FileUpload::getInstance().get_packet(report_log_msg.data.packet_num, report_log_msg.command_id);
            }
    } else if((0 == msg_head.command.compare("CMD_GET_LOG")) && (0 == msg_head.command_type.compare("REP"))){
    } else {
        CLogger::getInstance()->log_i("异常消息:%s-%s", msg_head.command.c_str(), msg_head.command_type.c_str());
        return -1;
    }
    return 0;
}
//MQTT回调函数
int callback(CALLBACK_TYPE cb_type, void *data, int data_len, int result, void *arg){
    CLogger::getInstance()->log_d("云边通信回调方法(%d)", cb_type);
    try{
        
        if(cb_type == CALLBACK_TYPE_CONNECTED){
            subscribe_topics();
            g_is_connected = true;
            g_keepalive_thread_notify.lock();
            g_keepalive_thread_notify.signal();
            g_keepalive_thread_notify.unlock();            
        } else if(cb_type == CALLBACK_TYPE_MESSAGE){
            struct mosquitto_message *msg = (struct mosquitto_message *)data;
            std::string payload = (char *)msg->payload;
            std::string c2d_topic = CLOUD_TO_DEV_TOPIC(std::string(g_sn));
            CLogger::getInstance()->log_i("req msg: %s", payload.c_str());
            if(0 == strcmp(msg->topic, c2d_topic.c_str())){
                handle_message(payload);
            }
        } else if(cb_type == CALLBACK_TYPE_DISCONNECTED){
            g_is_connected = false;
        }
    } catch(...){
        result = -1;
    }

    CLogger::getInstance()->log_d("%s{%d}", (char *)data, result);
    return result;
}
void init_mosquitto(){
    if(MOSQ_ERR_SUCCESS == g_mosquitto_manager->init(NULL, callback)){
        CLogger::getInstance()->log_i("cloud addr: %s, port: %d, username: %s, password: %s", cloud_mosquitto_addr, cloud_mosquitto_port, cloud_username, cloud_password);
        int ret = g_mosquitto_manager->connect(cloud_mosquitto_addr, cloud_mosquitto_port, cloud_username, cloud_password, 10);
        if(MOSQ_ERR_SUCCESS == ret){
            //配置主题需要沟通后修改
            g_mosquitto_manager->start();
        }else{
            g_mosquitto_manager->stop();
            sleep(2);
            init_mosquitto();
        }
    }
}

int send_upgrade_state(uint64_t req_id, int code, int64_t upgrade_id, std::string msg){
    if(nullptr == g_mosquitto_manager){
        return -1;
    }
    std::string d2c_topic = DEV_TO_CLOUD_TOPIC(std::string(g_sn));
    UPGRADE_REP_MSG_TYPE rep;
    rep.command = "CMD_UPGRADE_STATE_REPORT";
    rep.command_id = req_id;
    rep.command_type = "REQ";
    rep.sn = g_sn;
    rep.timestamp = get_timestamp();
    rep.data.code = code;
    rep.data.msg = msg;
    rep.data.upgrade_id = upgrade_id;
    std::string rep_msg = JsonManager::getInstance().get_response_msg(rep);
    return g_mosquitto_manager->publish(d2c_topic.c_str(), rep_msg.length(), rep_msg.c_str());
}
/*
 1-协议引擎APP；2-串口代理APP；3-数据中心APP；4-系统固件；100-未知目标
*/
void thread_keepalive(int t){
    uint64_t req_id = 0;
    std::string d2c_topic = DEV_TO_CLOUD_TOPIC(std::string(g_sn));
    while(true){
        
        if(false == g_is_connected){
            g_keepalive_thread_notify.lock();
            g_keepalive_thread_notify.wait();
            g_keepalive_thread_notify.unlock();
        }
        read_version();
        ONLINE_MSG_TYPE online;
        online.command = "CMD_EDGE_ONLINE";
        online.command_id = req_id++;
        online.command_type = "REQ";
        online.sn = g_sn;
        online.timestamp = get_timestamp();
        VERSION_DATA ver_data;
        ver_data.code = pemd_code;
        ver_data.target = 1;
        ver_data.ver = pemd_ver;
        online.data.push_back(ver_data);
        ver_data.code = uartproxy_code;
        ver_data.target = 2;
        ver_data.ver = uartproxy_ver;
        online.data.push_back(ver_data);
        ver_data.code = datacenter_code;
        ver_data.target = 3;
        ver_data.ver = datacenter_ver;
        online.data.push_back(ver_data);
        ver_data.code = firmware_code;
        ver_data.target = 4;
        ver_data.ver = firmware_ver;
        online.data.push_back(ver_data);
        std::string online_msg = JsonManager::getInstance().get_online_msg(online);
        g_mosquitto_manager->publish(d2c_topic.c_str(), online_msg.length(), online_msg.c_str());
        CLogger::getInstance()->log_d("online: %s", online_msg.c_str());
        sleep(t);
    }
}

void run_keepalive(){
    std::thread online_thread(thread_keepalive, keepalive_time);
    online_thread.detach();
}

int main(int argc, char *argv[]){
    char dir[FILENAME_LEN] = {0};
    char curTimeStr[CURRENT_TIME_STRING_LEN] = {0};
    
    if(0 > parse_cmd(argc, argv)){
        return -1;
    }
    CLogger::getInstance()->init(log_filename, log_size, "upgrade", log_level);
    init_settings();
    mqtt_connect(mosquitto_addr, mosquitto_port);
    get_dev_sn(g_sn, sizeof(g_sn));
    init_mosquitto();
    FileUpload::getInstance().init(g_mosquitto_manager, g_sn);

    run_keepalive();
    uint64_t req_id = 1;

    while(true){
        g_upgrade_thread_notify.lock();
        g_upgrade_thread_notify.wait();
        send_response(g_upgrade_req_msg.command, DEV_TO_CLOUD_TOPIC(std::string(g_sn)), g_upgrade_req_msg.command_id, 0, g_upgrade_req_msg.data.upgrade_id, "收到升级请求");
        get_current_time_str(curTimeStr);
        get_dir(dir, curTimeStr);
        get_file_name(curTimeStr,filename);
        if(CURLE_OK == download(g_upgrade_req_msg, filename)){
            if(0 == upgrade(filename, dir)){
                //上报升级成功
                send_upgrade_state(req_id, 2, g_upgrade_req_msg.data.upgrade_id, "升级成功");
            } else {
                //上报升级失败
                send_upgrade_state(req_id, 3, g_upgrade_req_msg.data.upgrade_id,"升级失败");
            }
        } else {
            //上报升级失败
                send_upgrade_state(req_id, 3, g_upgrade_req_msg.data.upgrade_id,"升级包下载失败");
        }
        req_id++;
        g_upgrade_thread_notify.unlock();
    }
    
    return 0;
}